package com.test.util;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.test.constants.Erc20Constants;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.message.BasicHeader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.web3j.abi.FunctionEncoder;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.Function;
import org.web3j.abi.datatypes.Type;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.RawTransaction;
import org.web3j.crypto.TransactionEncoder;
import org.web3j.crypto.WalletUtils;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.Response;
import org.web3j.protocol.core.methods.response.*;
import org.web3j.tx.Contract;
import org.web3j.tx.Transfer;
import org.web3j.utils.Convert;
import org.web3j.utils.Convert.Unit;
import org.web3j.utils.Numeric;

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;

/**
 * 基于web3j EthUtil 工具类
 */
public class ETHUtil {

    private static final Logger logger = LoggerFactory.getLogger(ETHUtil.class);

    // 查询gas_price
 	private static final String GAS_PRICE_URL = "https://ethgasstation.info/json/ethgasAPI.json";
 	// 最多打给用户0.01个eth
    private static final BigDecimal COST_GAS_LIMIT = new BigDecimal("0.01");
    // 默认gas price
    private static final BigInteger GAS_PRICE_DEFAUL = new BigInteger("20000000000");
    
    /**
     * 创建账户
     * 
     * @param password
     * @return 账号 address 和 凭证文件名
     */
    public static String[] newAccount(String password) {
        logger.info("enter method newAccount()");
        String[] result = null;
        try {
            // 如果路径不存在，则先创建文件夹
            File filePath = new File(Erc20Constants.WALLET_FILE_PATH);
            if (!filePath.exists()) {
                filePath.mkdir();
            }
            // 创建钱包文件
            String walletFileName = WalletUtils.generateNewWalletFile(password, filePath, false);
            if (StringUtils.isEmpty(walletFileName)) {
                logger.error("newAccount_create wallet file fail.");
                return null;
            }
            // 加载凭证（钱包文件全文件名）
            String walletFileAbsName = Erc20Constants.WALLET_FILE_PATH + File.separator + walletFileName;
            Credentials credentials = WalletUtils.loadCredentials(password, walletFileAbsName);
            if (credentials != null) { // 凭证不为空，获取地址
                logger.info("newAccount_create account success.");
                result = new String[2];
                result[0] = credentials.getAddress();
                result[1] = walletFileName;
                return result;
            }
        } catch (Exception e) {
            logger.error("newAccount_exception:{}", e);
        }
        return null;
    }
    
    /**
     * 查询账户余额
     * 
     * @param address
     * @return
     */
    public static BigDecimal balanceOf(String address) {
        EthGetBalance ethGetBalance = null;
        try {
            ethGetBalance = Web3JClient.getWeb3j().ethGetBalance(address, DefaultBlockParameterName.LATEST).send();
            if (ethGetBalance != null) {
                logger.info("balanceOf_{}_balance: {} eth.", address, ethGetBalance.getBalance().toString());
                // 单位转换：ETH精度是18（ETHER）(ehterscan上查询contract可得)
                return Convert.fromWei(ethGetBalance.getBalance().toString(), Unit.ETHER);
            }
        } catch (IOException e) {
            logger.error("balanceOf_{}_exception:{}", address, e);
        }
        return null;
    }

    /**
     * 转账（通过密码和钱包秘钥文件）
     * 
     * @param toAddress
     * @param password
     * @param walletFileName
     * @param amount
     * @return 交易hash
     */
    public static String sendFunds(String toAddress, String password, String walletFileName, BigDecimal amount) {
        logger.info("enter method sendFunds(). toAddress:{}, amount:{}", toAddress, amount);
        try {
            // 通过钱包文件加载凭证
            String walletFileAbsName = Erc20Constants.WALLET_FILE_PATH + File.separator + walletFileName;
            Credentials credentials = WalletUtils.loadCredentials(password, walletFileAbsName);
            TransactionReceipt transactionReceipt = 
    					Transfer.sendFunds(Web3JClient.getWeb3j(),
    									   credentials, 
    									   toAddress,
                                           amount.setScale(Erc20Constants.AMOUNT_SCALE, BigDecimal.ROUND_HALF_UP), // 保留小数点8位，四舍五入
                                           Unit.ETHER)
                                .send(); // send()方法是同步的，等交易成功之后再返回；sendAsync()方法是异步的，返回一个Future。
            if (transactionReceipt != null) {
                logger.info("sendFunds_txHash_{}", transactionReceipt.getTransactionHash());
                return transactionReceipt.getTransactionHash();
            }
        } catch (Exception e) {
            logger.error("sendFunds_exception:{}", e);
        }
        return null;
    }

    /**
     * 转账（通过privateKey）
     * 
     * @param toAddress
     * @param privateKey
     * @param amount
     * @return 交易hash
     */
    public static String sendFunds(String toAddress, String privateKey, BigDecimal amount) {
        logger.info("enter method sendFunds(). toAddress:{}, amount:{}", toAddress, amount);
        try {
            // 通过privateKey加载凭证
            Credentials credentials = Credentials.create(privateKey);
            TransactionReceipt transactionReceipt = Transfer.sendFunds(
                    Web3JClient.getWeb3j(),
                    credentials,
                    toAddress,
                    amount,
                    Unit.ETHER)
                    .send(); // send()方法是同步的，等交易成功之后再返回；sendAsync()方法是异步的，返回一个Future。
            if (transactionReceipt != null) {
                logger.info("sendFunds_txHash_{}", transactionReceipt.getTransactionHash());
                return transactionReceipt.getTransactionHash();
            }
        } catch (Exception e) {
            logger.error("sendFunds_exception:{}", e);
        }
        return null;
    }

    /**
     * 根据交易hash，查询交易详情
     *
     * @param txHash
     * @return 交易详情
     */
    public static TransactionReceipt getTxDetail(String txHash) {
        logger.info("enter method getTxDetail(). txHash:{}", txHash);
        try {
            EthGetTransactionReceipt ethGetTransactionReceipt = Web3JClient.getWeb3j().ethGetTransactionReceipt(txHash).send();
            if (ethGetTransactionReceipt != null && ethGetTransactionReceipt.getResult() != null) {
                TransactionReceipt transactionReceipt = ethGetTransactionReceipt.getResult();
                return transactionReceipt;
            }
        } catch (IOException e) {
            logger.error("getTxDetail_txHash_{} exception:{}", txHash, e);
        }
        return null;
    }

    /**
     * 根据交易hash，查询交易状态
     * 
     * @param txHash
     * @return 交易状态：false：失败，true：成功
     */
    public static boolean isTxConfirmed(String txHash) {
        logger.info("enter method isTxConfirmed(). txHash:{}", txHash);
        try {
            EthGetTransactionReceipt ethGetTransactionReceipt = Web3JClient.getWeb3j().ethGetTransactionReceipt(txHash).send();
            if (ethGetTransactionReceipt != null && ethGetTransactionReceipt.getResult() != null) {
                System.out.println("result:" + JSON.toJSON(ethGetTransactionReceipt.getResult()));
                String txStatus = ethGetTransactionReceipt.getResult().getStatus();
                logger.info("isTxConfirmed_txHash:{}_txStatus:{}", txHash, txStatus);
                if ("0x1".equals(txStatus)) { // 成功
                    logger.info("isTxConfirmed_txHash_{} txStatus is true.", txHash);
                    return true;
                } else { // 失败
                    logger.info("isTxConfirmed_txHash_{} txStatus is false.", txHash);
                    return false;
                }
            }
        } catch (IOException e) {
            logger.error("isTxConfirmed_txHash_{} exception:{}", txHash, e);
        }
        return false;
    }

    /**
     * 根据交易hash，查询交易状态是否为失败
     * 
     * @param txHash
     * @return 交易状态：true：是失败状态，false：不是失败状态
     */
    public static boolean isTxFail(String txHash) {
        logger.info("enter method isTxFail(). txHash:{}", txHash);
        try {
            EthGetTransactionReceipt ethGetTransactionReceipt = Web3JClient.getWeb3j().ethGetTransactionReceipt(txHash).send();
            if (ethGetTransactionReceipt != null && ethGetTransactionReceipt.getResult() != null) {
                String txStatus = ethGetTransactionReceipt.getResult().getStatus();
                logger.info("getTxStatusByHash_txHash_{}, txStatus{}", txHash, txStatus);
                if ("0x0".equals(txStatus)) { // 是失败状态
                    logger.info("getTxStatusByHash_txHash_{} txStatus is fail.", txHash);
                    return true;
                } else { // 不是失败状态
                    logger.info("getTxStatusByHash_txHash_{} txStatus is false.", txHash);
                    return false;
                }
            }
        } catch (IOException e) {
            logger.error("getTxStatusByHash_txHash_{} exception:{}", txHash, e);
        }
        return false;
    }

    /**
     * ERC20转账（rawTransaction）
     * https://www.jianshu.com/p/bc1dc923f741
     * https://blog.csdn.net/shebao3333/article/details/80078010
     * 
     * @param contractAddress
     * @param toAddress
     * @param password
     * @param walletFileName
     * @param amount
     * @return 交易hash
     */
    @SuppressWarnings("rawtypes")
    public static String transfer(String contractAddress, String toAddress, String password, String walletFileName, BigInteger amount) {
        logger.info("enter method transfer(). toAddress:{}, amount:{}", toAddress, amount);
        String transactionHash = ""; // 交易结果hash
        try {
            // 通过钱包文件加载凭证
            String walletFileAbsName = Erc20Constants.WALLET_FILE_PATH + File.separator + walletFileName;
            Credentials credentials = WalletUtils.loadCredentials(password, walletFileAbsName);
            String fromAddress = credentials.getAddress();
            EthGetTransactionCount ethGetTransactionCount = Web3JClient.getWeb3j()
                                                               .ethGetTransactionCount(fromAddress, DefaultBlockParameterName.LATEST)
                                                               .sendAsync().get();
            BigInteger nonce = ethGetTransactionCount.getTransactionCount();
            Address address = new Address(toAddress);
            Uint256 value = new Uint256(amount);
            List<Type> parametersList = new ArrayList<>();
            parametersList.add(address);
            parametersList.add(value);
            List<TypeReference<?>> outList = new ArrayList<>();
            Function function = new Function("transfer", parametersList, outList);
            String data = FunctionEncoder.encode(function);
            RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, Contract.GAS_PRICE, Contract.GAS_LIMIT, contractAddress,
                                                                             data);
            byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
            String hexValue = Numeric.toHexString(signedMessage);
            EthSendTransaction ethSendTransaction = Web3JClient.getWeb3j().ethSendRawTransaction(hexValue).sendAsync().get();
            if (ethSendTransaction != null) {
                transactionHash = ethSendTransaction.getTransactionHash();
            }

        } catch (Exception e) {
            logger.error("transfer_exception:{}", e);
            return null;
        }
        return transactionHash;
    }

    /**
     * eth交易签名
     * 
     * @param password
     * @param walletFileName
     * @param to
     * @param data
     * @param value
     * @param gasPrice
     * @param gasLimit
     * @param nonce
     * @return 签名信息
     */
    public static String ethSign(String password, String walletFileName, String to, String data, BigInteger value, BigInteger gasPrice,
            BigInteger gasLimit, BigInteger nonce) {
        logger.info("enter method ethSign(). to:{}, value:{}", to, value);
        try {
            // 通过钱包文件加载凭证
            String walletFileAbsName = Erc20Constants.WALLET_FILE_PATH + File.separator + walletFileName;
            Credentials credentials = WalletUtils.loadCredentials(password, walletFileAbsName);
            // 创建rawTransaction
            RawTransaction rawTransaction = RawTransaction.createTransaction(nonce, gasPrice, gasLimit, to, value, data);
            // 对rawTransaction进行签名
            byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials);
            // 转换成16进制结果返回
            String signData = Numeric.toHexString(signedMessage);
            return signData;
        } catch (Exception e) {
            logger.error("ethSign_exception:{}", e);
        }
        return null;
    }

    /**
     * 判断eth地址有效性
     * 
     * @param address
     * @return
     */
    public static boolean vailedAddress(String address) {
        logger.info("enter method vailedAddress(). address:{}", address);
        if (StringUtils.isEmpty(address) || !address.startsWith("0x")) {
            return false;
        }
        boolean result = WalletUtils.isValidAddress(address);
        logger.info("vailedAddress_验证地址的有效性成功。 result:{}", result);
        return result;
    }

    /**
     * 获取当前gas_price
     * （通过web3j接口获取）
     */
    public static BigInteger getGasPriceByWeb3j() {
        logger.info("enter method getCurGasPrice().");
        try {
            EthGasPrice ethGasPrice = Web3JClient.getWeb3j().ethGasPrice().send();
            BigInteger curGasPrice = ethGasPrice.getGasPrice();
            logger.info("getGasPriceByWeb3j_curGasPrice:{}", curGasPrice);
            return curGasPrice;
        } catch (IOException e) {
            logger.error("getGasPriceByWeb3j_exception: " + e.getMessage(), e);
        }
        return null;
    }

    /**
     * 获取当前gas_price
     * （通过api获取：https://ethgasstation.info/json/ethgasAPI.json）
     */
    public static BigInteger getGasPriceByGasApi() {
    	BigInteger gasPrice = GAS_PRICE_DEFAUL; // 默认值

    	try {
			List<Header> headers = new ArrayList<Header>();
			headers.add(new BasicHeader("Content-Type", "application/json;charset=utf-8"));
	        headers.add(new BasicHeader("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.3"));
			String resJson = HttpClientUtil.doGet(GAS_PRICE_URL, headers);
			logger.info("getGasPriceByApi_resJson:{}", resJson);
			if(StringUtils.isEmpty(resJson)) {
				return getGasPriceByWeb3j();
			}
			JSONObject jsonObj = JSON.parseObject(resJson);
			if(jsonObj==null) {
				return getGasPriceByWeb3j();
			}
			int fastest = jsonObj.getIntValue("fastest");
			if(fastest>0) {
				gasPrice = com.test.util.BigIntegerConvert.toWei(String.valueOf(fastest), com.test.util.BigIntegerConvert.Unit.EIGHT);
			}
			return gasPrice;
		} catch (Exception e) {
			logger.error("getGasPriceByApi_exception:{}", e);
		}
        return gasPrice;
    }
	
	/**
	 * 获取当前最新的eth手续费上限
	 * @param gasLimit gas数量上限
	 * @return 手续费，单位：eth
	 */
	public static BigDecimal getCostGasLimit4Erc20(BigInteger gasLimit) {
		BigDecimal costGasLimit = null;
		try {
			BigInteger gasPrice = getGasPriceByGasApi();
    		if(gasPrice==null) {
    			return null;
    		}
    		
    		// 花费的gas = gasPrice * gasLimit；
			BigInteger costGas = gasPrice.multiply(gasLimit);
			if(costGas!=null) {
				costGasLimit = Convert.fromWei(costGas.toString(), Unit.ETHER);
				if(costGasLimit.compareTo(COST_GAS_LIMIT)>0) {
					costGasLimit = COST_GAS_LIMIT; // 手续费最多:COST_GAS_LIMIT
				}
    		}
		} catch (Exception e) {
			logger.error("getCostGasLimit4Erc20_exception:{}", e);
		}
		
		logger.info("getCostGasLimit4Erc20_costGasLimit:{} eth.", costGasLimit);
		return costGasLimit;
	}
	

    /**
     * main
     * 
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        // 获取节点运行geth客户端的版本号
//        Web3ClientVersion web3ClientVersion = Web3JClient.getWeb3j().web3ClientVersion().sendAsync().get();
//        String clientVersion = web3ClientVersion.getWeb3ClientVersion();
//        System.out.println("clientVersion: " + clientVersion);

        // 返回当前节点持有的帐户列表
//        EthAccounts ethAccounts = Web3JClient.getWeb3j().ethAccounts().sendAsync().get();
//        List<String> accountList = ethAccounts.getAccounts();
//        System.out.println("accountList.size: " + accountList.size());
//        if(!CollectionUtils.isEmpty(accountList)) {
//            accountList.forEach(account -> {
//               System.out.println(account);
//            });
//        }

        // 创建账号
//        String[] addressAndWalletFile = newAccount(Erc20Constants.PASSWORD_ETH);
//        if(addressAndWalletFile!=null && addressAndWalletFile.length==2) {
//            System.out.println("address:" + addressAndWalletFile[0]);
//            System.out.println("walletFile:" + addressAndWalletFile[1]);
//        }

        // 查询余额
//        String address = "0x80fb7894d930afd97c8ccc9b7191f0be2505ae63";
//        BigDecimal balance = balanceOf(address);
//        System.out.println("ETH balance of "+address+" is : "+balance.toPlainString());

        // 查询当前gasprice
//        System.out.println("getGasPrice:" + BigIntegerConvert.fromWei(getGasPrice(), BigIntegerConvert.Unit.GWEI) + "gwei");
//        System.out.println("getGasPriceByWeb3j:" + BigIntegerConvert.fromWei(getGasPriceByWeb3j(), BigIntegerConvert.Unit.GWEI) + "gwei");
//        System.out.println("getGasPriceByGasApi:" + BigIntegerConvert.fromWei(getGasPriceByGasApi(), BigIntegerConvert.Unit.GWEI) + "gwei");

//        // 查询交易状态
//         String txId = "0x1c41fafc183dc41bc4377a8d0d32bf05fef77b02a848165e50c11c7aa42c70a6";
//         boolean txStatus = isTxConfirmed(txId);
//         System.out.println("txStatus:" + txStatus);

        // 查询交易详情
//        String txId = "0x1c41fafc183dc41bc4377a8d0d32bf05fef77b02a848165e50c11c7aa42c70a6";
//        TransactionReceipt txDetail = getTxDetail(txId);
//        System.out.println("txDetail:" + JsonUtil.formatJson(JsonUtil.encodeJson(txDetail)));

        // 通过密钥文件转账
//        String toAddress = "0xF2C9D965239eB23645c2cbd96Ac8b7Adf616112d";
//        String walletFileName = "UTC--2021-07-25T13-23-13.857000000Z--80fb7894d930afd97c8ccc9b7191f0be2505ae63.json";
//        BigDecimal amount = new BigDecimal(5);
//        String txHash = sendFunds(toAddress, Erc20Constants.PASSWORD_ETH, walletFileName, amount);
//        System.out.println("txHash:" + txHash);

        // 通过privateKey转账
//        String toAddress = "0x80fb7894d930afd97c8ccc9b7191f0be2505ae63";
//        String fromPrivateKey = "0x33a191f03d755dd13ac73915787a2badc1c39f52c17da5816aebfce682c85310";
//        BigDecimal amount = new BigDecimal(20);
//        String txHash = sendFunds(toAddress, fromPrivateKey, amount);
//        System.out.println("txHash:" + txHash);
    }
}
